﻿/*
 * Jonathan
 * Created by: Jonathan Montiverdi
 * Date: 6/13/2012
 * License: http://jonathan.codeplex.com/license
 */
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

using Jonathan.Data;
using Jonathan.Logic.Services;
using Monti.Docking;
using Monti.MVVM;
using Monti.MVVM.Services;

namespace Jonathan.Logic
{
   /// <summary>
   /// The main view model for the Jonathan application.
   /// </summary>
   public class MainViewModel : ViewModelBase, IMain
   {
      #region Fields

      private object m_ActiveDockItem;
      
      private string m_Title;
      private string m_SearchText;
      
      private RelayCommand m_CmdClose;
      private RelayCommand m_CmdLoaded;
      private RelayCommand m_CmdSearch;
      private RelayCommand m_CmdNavigation;
      
      private DockGroup m_TopToolWindows;
      private DockGroup m_LeftToolWindows;
      private DockGroup m_RightToolWindows;
      private DockGroup m_BottomToolWindows;
      private CustomTemplateSelector m_DockingTemplateSelector;
      public event EventHandler Disposing;

      #endregion
      
      #region Setup

      /// <summary>
      /// Initializes a new main view model object.
      /// </summary>
      public MainViewModel ()
      {
         this.Title = "Jonathan";
         this.SearchHistory = new SearchHistory ();
         ServiceManager.RegisterSingleton <IMain, MainViewModel> (this);
      }

      /// <summary>
      /// Event that occurs when the main window is loaded.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      private void OnLoaded (object obj)
      {
         this.Options = new Options ();
         ServiceManager.RegisterSingleton <IOptions, Options> (this.Options);
         GuiDispatcher = Dispatcher.CurrentDispatcher;
         this.AddDataTemplates();
         this.Options.Load ();
         
         this.LeftToolWindows = new DockGroup ();
         this.LeftToolWindows.Items.Add (new BookListViewModel ());
         
         this.DownloadMainDb();
         // TODO: Startup Tab...
      }

      #endregion

      #region Properties

      /// <summary>
      /// Gets the main thread's dispatcher object.
      /// </summary>
      public static Dispatcher GuiDispatcher { get; private set; }
      
      /// <summary>
      /// Gets the options for Jonathan.
      /// </summary>
      public Options Options { get; private set; }

      /// <summary>
      /// Gets or Sets the title of the main window.
      /// </summary>
      public string Title
      {
         get { return this.m_Title; }
         set
         {
            this.m_Title = value;
            this.RaisePropertyChanged ("Title");
         }
      }
      
      /// <summary>
      /// Gets or Sets the search text.
      /// </summary>
      public string SearchText
      {
         get { return this.m_SearchText; }
         set
         {
            if (this.m_SearchText != value)
            {
               this.m_SearchText = value;
               this.RaisePropertyChanged ("SearchText");
            }
         }
      }
      
      /// <summary>
      /// Gets the search history.
      /// </summary>
      public SearchHistory SearchHistory { get; private set;}

      /// <summary>
      /// Gets the collection of documents
      /// </summary>
      public DockGroup Documents { get; set; }

      /// <summary>
      /// Gets or Sets the active dock item.
      /// </summary>
      public object ActiveDockItem
      {
         get { return this.m_ActiveDockItem; }
         set
         {
            this.m_ActiveDockItem = value;
            this.RaisePropertyChanged ("ActiveDockItem");
         }
      }

      /// <summary>
      /// Gets or Sets the set of left tool windows.
      /// </summary>
      public DockGroup LeftToolWindows
      {
         get { return this.m_LeftToolWindows; }
         set
         {
            this.m_LeftToolWindows = value;
            this.RaisePropertyChanged ("LeftToolWindows");
         }
      }

      /// <summary>
      /// Gets or Sets the set of right tool windows.
      /// </summary>
      public DockGroup RightToolWindows
      {
         get { return this.m_RightToolWindows; }
         set
         {
            this.m_RightToolWindows = value;
            this.RaisePropertyChanged ("RightToolWindows");
         }
      }

      /// <summary>
      /// Gets or Sets the set of top tool windows.
      /// </summary>
      public DockGroup TopToolWindows
      {
         get { return this.m_TopToolWindows; }
         set
         {
            this.m_TopToolWindows = value;
            this.RaisePropertyChanged ("TopToolWindows");
         }
      }

      /// <summary>
      /// Gets or Sets the set of bottom tool windows.
      /// </summary>
      public DockGroup BottomToolWindows
      {
         get { return this.m_BottomToolWindows; }
         set
         {
            this.m_BottomToolWindows = value;
            this.RaisePropertyChanged ("BottomToolWindows");
         }
      }

      /// <summary>
      /// Gets the template selector.
      /// </summary>
      public CustomTemplateSelector DockingTemplateSelector
      {
         get { return this.m_DockingTemplateSelector; }
         set
         {
            this.m_DockingTemplateSelector = value;
            this.RaisePropertyChanged ("DockingTemplateSelector");
         }
      }

      /// <summary>
      /// Gets the command that is executed when the loaded event occurs.
      /// </summary>
      public ICommand LoadedCommand
      {
         get { return this.m_CmdLoaded ?? (this.m_CmdLoaded = new RelayCommand (this.OnLoaded)); }
      }

      /// <summary>
      /// Gets the closing command to execute.
      /// </summary>
      public ICommand ClosingCommand
      {
         get { return this.m_CmdClose ?? (this.m_CmdClose = new RelayCommand (this.OnClose)); }
      }
      
      /// <summary>
      /// Gets the search command.
      /// </summary>
      public ICommand SearchCommand
      {
         get { return this.m_CmdSearch ?? (this.m_CmdSearch = new RelayCommand (this.OnSearch)); }
      }
      
      /// <summary>
      /// Gets the wiki navigation command.
      /// </summary>
      public ICommand NavigationCommand
      {
         get { return this.m_CmdNavigation ?? (this.m_CmdNavigation = new RelayCommand (this.OnNavigation, this.CanNavigate)); }
      }

      #endregion
      
      #region Methods

      /// <summary>
      /// Downloads the main database from the wiki server.
      /// </summary>
      private void DownloadMainDb()
      {
         // TODO: List new books or prompt the user to take a look at them.
         if (!string.IsNullOrEmpty(this.Options.Configuration.MainDbLastDlTime))
         {
            var now = DateTime.Now;
            var lastDlTime = DateTime.Parse(this.Options.Configuration.MainDbLastDlTime);
            if (lastDlTime.Day == now.Day && lastDlTime.Month == now.Month && lastDlTime.Year == now.Year)
            {
               this.UpdateBooks (null, EventArgs.Empty);
               return;
            }
         }
         else
         {
            var dataDirectory = new DirectoryInfo (Common.DATA_DIR);
            if (!dataDirectory.Exists) dataDirectory.Create ();
         }
         var download = Downloader.AddToQueue(Common.MAIN_DB, Common.MAIN_DB_PATH);
         download.Completed += this.UpdateBooks;
      }
      
      /// <summary>
      /// Performs the auto update funciton.
      /// </summary>
      private void UpdateBooks (object sender, EventArgs e)
      {
         var download =  sender as Download;
         if (download != null && download.WasSuccessful)
         {
            this.Options.Configuration.MainDbLastDlTime = DateTime.Now.ToString (CultureInfo.InvariantCulture);
            this.Options.Save ();
         }
         this.Options.LoadBooksFromMainDb ();
         foreach (var book in this.Options.Books.Where (b => b.UpdateAutomatically))
            Downloader.AddToQueue (book.FileName, Common.DATA_DIR + book.FileName);
      }

      /// <summary>
      /// Adds the data templates for the different gui items.
      /// </summary>
      void AddDataTemplates ()
      {
         var mainWindow = Application.Current.MainWindow;
         var template = mainWindow.FindResource ("DataViewerTemplate") as DataTemplate;
         this.DockingTemplateSelector.DataTemplates.Add(typeof (ReaderViewModel), template);
         this.DockingTemplateSelector.DataTemplates.Add(typeof (SearchViewModel), template);
         this.DockingTemplateSelector.DataTemplates.Add(typeof (LibraryViewModel), template);
         this.DockingTemplateSelector.DataTemplates.Add(typeof (BookListViewModel), template);
         this.DockingTemplateSelector.DataTemplates.Add(typeof (DefinitionViewModel), template);
         this.DockingTemplateSelector.DataTemplates.Add(typeof (WikiUploadViewModel),
                                                        mainWindow.FindResource ("WikiUploadTemplate") as DataTemplate);
      }
      
      /// <summary>
      /// Checks to see if a navigation command can execute.
      /// </summary>
      /// <param name="parameter">Command parameter.</param>
      private bool CanNavigate (object parameter)
      {
         return true;
      }
      
      /// <summary>
      /// Executes a navigation command.
      /// </summary>
      /// <param name="parameter">Command parameter.</param>
      private void OnNavigation (object parameter)
      {
         var param = parameter.ToString ();
         if (param == "Uploader")
         {
            this.AddDocument (new WikiUploadViewModel ());
         }
         else if (param == "Converter")
         {
            var converter = ServiceManager.Resolve <IWikiConverterService> ();
            converter.Show ();
         }
         else if (param == "Library")
         {
            this.ShowLibrary ();
         }
      }
      
      /// <summary>
      /// Creates a new reader with the given book.
      /// </summary>
      /// <param name="book">Book to read.</param>
      public void CreateNewReader (Book book)
      {
         this.AddDocument (new ReaderViewModel (book));
      }
      
      /// <summary>
      /// Shows the library/book manager.
      /// </summary>
      public void ShowLibrary ()
      {
         this.AddDocument (new LibraryViewModel ());
      }
      
      /// <summary>
      /// Shows a definition.
      /// </summary>
      /// <param name="word">Word we are trying to define.</param>
      public void ShowDefinition (string word)
      {
         if (this.Options.AvailableBooks.Any (b => b.Name.Equals (word, StringComparison.CurrentCultureIgnoreCase)))
         {
            return;
         }
         
         if (!this.Options.Books.Any (b => b.Name == Common.DEF_DB_NAME))
         {
            var message = ServiceManager.Resolve <IMessageBoxService> ();
            if (!message.ShowYesNo ("Definition database was not found, is it ok to download it?", "Jonathan"))
               return;
            
            this.Options.Books.Add (this.Options.AvailableBooks.FirstOrDefault (b => b.Name == Common.DEF_DB_NAME));
            var download = Downloader.AddToQueue (Common.DEF_DB, Common.DEF_DB_PATH);
            download.Completed += delegate { ShowDefinition (word); };
            return;
         }
         var def = new DefinitionViewModel (word);
         if (def.Definitions.Count <= 0)
         {
            var message = ServiceManager.Resolve <IMessageBoxService> ();
            if (message.ShowYesNo (word + " has not been definied yet, would you like to add it to the Wiki?", "Jonathan"))
            {
               // TODO: Change uploader to hide text fields not being used by IsPage...
               var wikiUpload = new WikiUploadViewModel { IsPage = true, PageTitle = word };
               this.AddDocument (wikiUpload);
            }
         }
         else
         {
            if (this.RightToolWindows == null) this.RightToolWindows = new DockGroup ();
            this.RightToolWindows.Items.Add (def);
         }
      }
      
      /// <summary>
      /// Adds a document item to the dock manager.
      /// </summary>
      /// <param name="item">Item to add.</param>
      public void AddDocument (object item)
      {
         if (this.Documents == null) this.Documents = new DockGroup ();
         this.Documents.Items.Add (item);
      }
      
      /// <summary>
      /// Shows a search result with the given text.
      /// </summary>
      /// <param name="text">Text we are searching for.</param>
      public void Search (string text)
      {
         var search = new SearchViewModel ();
         search.Search (text);
         this.AddDocument (search);
      }
      
      /// <summary>
      /// Executes the search command;
      /// </summary>
      /// <param name="parameter">Command parameter.</param>
      private void OnSearch (object parameter)
      {
         this.SearchHistory.AddItem (this.SearchText);
         this.Search (this.SearchText);
      }

      /// <summary>
      /// Occurs when the main window is closing.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      private void OnClose (object obj)
      {
         this.Dispose ();
      }
      
      /// <summary>
      /// Disposes the object.
      /// </summary>
      public void Dispose ()
      {
         if (this.Disposing != null)
            this.Disposing (this, new EventArgs ());
         if (Downloader.HasInstance)
            Downloader.Instance.Dispose ();
      }
      
      #endregion
   }
}
